Spring Cloud Netflix Zuul中的速率限制
来源:SpringForAll社区
1.引言
Spring Cloud Netflix Zuul 是一个包含Netflix Zuul的开源网关。它为Spring Boot应用增加了一些特别的特性。不幸的是,开箱即用不提供速率限制。
在这篇教程中,我们将探索增加了速率限制请求的Spring Cloud Zuul RateLimit。
2.Maven配置
除了Spring Cloud Netflix Zuul的依赖,我们需要增加Spring Cloud Zuul RateLimit到我们应用的pom.xml。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
3.Controller例子
首先,我们在将要应用速率限制的地方创建一些REST端点。 下面是一个简单的有两个端点的Spring Controller类。
@Controller
@RequestMapping("/greeting")
public class GreetingController {
@GetMapping("/simple")
public ResponseEntity<String> getSimple() {
return ResponseEntity.ok("Hi!");
}
@GetMapping("/advanced")
public ResponseEntity<String> getAdvanced() {
return ResponseEntity.ok("Hello, how you doing?");
}
}
就像我们看到的,在速率限制的端点上没有特别的代码。这是因为我们将要配置这些速率限制在我们的Zuul 配置文件application.yml上。因此,保持我们的代码解耦。
4.Zuul配置
第二步,我们增加如下Zuul 配置到我们的application.yml文件上。
zuul:
routes:
serviceSimple:
path: /greeting/simple
url: forward:/
serviceAdvanced:
path: /greeting/advanced
url: forward:/
ratelimit:
enabled: true
repository: JPA
policy-list:
serviceSimple:
- limit: 5
refresh-interval: 60
type:
- origin
serviceAdvanced:
- limit: 1
refresh-interval: 2
type:
- origin
strip-prefix: true
在zuul.routes下,我们提供了端点详情。在zuul.ratelimit.policy-list下,我们为端点提供了速率限制配置。limit属性指定了端点在refresh-interval区间能被调用的次数。
就像我们看到的,我们为serviceSimple 端点增加了每60秒5次请求的速率限制。相反的,serviceAdvanced增加了每2秒1次请求的速率限制。
type配置制定了我们想要的速率限制方法。下面是一些可能的值:
origin-基于用户原始请求的速率限制
url-基于下游服务的请求路径的速率限制
user-基于认证用户名或匿名用户的速率限制
空值-每一个服务使用一个全局配置,要使用这个方式只需要不设置参数‘type’
5.测试速率限制
5.1.请求在限额内
接下来,我们测试速率限制
@Test
public void whenRequestNotExceedingCapacity_thenReturnOkResponse() {
ResponseEntity<String> response = restTemplate.getForEntity(SIMPLE_GREETING, String.class);
assertEquals(OK, response.getStatusCode());
HttpHeaders headers = response.getHeaders();
String key = "rate-limit-application_serviceSimple_127.0.0.1";
assertEquals("5", headers.getFirst(HEADER_LIMIT + key));
assertEquals("4", headers.getFirst(HEADER_REMAINING + key));
assertEquals("60000", headers.getFirst(HEADER_RESET + key));
}
这里我们创建一个到端点/greeting/simple的请求。这个请求在速率限制内是成功的。
另一个关键点是我们获得的每一个响应的响应头能提供我们更多的速率限制的信息。 对上面的请求,我们将得到如下的响应头。
X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 4
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 60000
换句话说:X-RateLimit-Limit-[key]: 端点的限制配置 X-RateLimit-Remaining-[key]: 调用端点的剩余尝试次数*X-RateLimit-Reset-[key]: 端点配置刷新间隔的剩余毫秒次数
另外,如果我们立即再次调用同样的端点,我们将得到
X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 3
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 57031
注意剩余调用次数和剩余调用毫秒时间已经减少。
5.2.请求在限额外
让我们看下超过限制速率将发生什么:
@Test
public void whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException {
ResponseEntity<String> response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
assertEquals(OK, response.getStatusCode());
for (int i = 0; i < 2; i++) {
response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
}
assertEquals(TOO_MANY_REQUESTS, response.getStatusCode());
HttpHeaders headers = response.getHeaders();
String key = "rate-limit-application_serviceAdvanced_127.0.0.1";
assertEquals("1", headers.getFirst(HEADER_LIMIT + key));
assertEquals("0", headers.getFirst(HEADER_REMAINING + key));
assertNotEquals("2000", headers.getFirst(HEADER_RESET + key));
TimeUnit.SECONDS.sleep(2);
response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
assertEquals(OK, response.getStatusCode());
}
这里我们快速连续调用/greeting/advanced 端点两次。因为我们配置了2秒只能1次请求的速率限制,第二次调用将会失败。错误码429(请求频繁)将作为结果返回给客户端。
下面是速率限制达到的时候返回的响应头:
X-RateLimit-Limit-rate-limit-application_serviceAdvanced_127.0.0.1: 1
X-RateLimit-Remaining-rate-limit-application_serviceAdvanced_127.0.0.1: 0
X-RateLimit-Reset-rate-limit-application_serviceAdvanced_127.0.0.1: 268
之后,我们休眠2秒,这是为端点刷新区间配置。最后,我们再次调用端点并能获得成功的响应。
6.定制秘钥生成器
我们能使用一个秘钥生成器来在响应头中定制秘钥发送。这是很有用的,因为应用可能需要除了可选的type属性之外的控制秘钥策略。
举例说明,通过创建一个定制的RateLimitKeyGenerator 实现。我们能添加更多的限定符或一些完全不一样的事情:
@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,
RateLimitUtils rateLimitUtils) {
return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
@Override
public String key(HttpServletRequest request, Route route,
RateLimitProperties.Policy policy) {
return super.key(request, route, policy) + "_" + request.getMethod();
}
};
}
上面的代码为秘钥追加了REST方法名。举例:
X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5
另一个关键点是RateLimitKeyGenerator对象将被spring-cloud-zuul-ratelimit自动配置.
7.个性化错误处理
框架支持针对速率限制数据存储的各种各样的实现。举例来说:Spring Data JPA 和 Redis 都有提供。默认的,失败将通过DefaultRateLimiterErrorHandler类作为错误记录下来。
当我们需要对错误进行不同处理时,我们能自定义RateLimiterErrorHandler 对象。
@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
return new DefaultRateLimiterErrorHandler() {
@Override
public void handleSaveError(String key, Exception e) {
// implementation
}
@Override
public void handleFetchError(String key, Exception e) {
// implementation
}
@Override
public void handleError(String msg, Exception e) {
// implementation
}
};
}
与RateLimitKeyGenerator 对象类似,RateLimiterErrorHandler 也会被自动配置。
8.总结
在这篇文章中,我们看到了怎样在Spring Cloud Netflix Zuul和Spring Cloud Zuul RateLimit中使用速率限制API。 一如既往,这篇文章的完整代码能在GitHub上找到。
原文链接:https://www.baeldung.com/spring-cloud-zuul-rate-limit
作者:Ganesh Pagade
译者:shirehappy
-END-
近期热文:
从内部自用到对外服务,配置管理的演进和设计优化实践
Spring Cloud Data Flow 中的 ETL
如何判断一个元素在亿级数据中是否存在?
Minor GC、Major GC和Full GC之间的区别
15个Spring的核心注释示例
Log4j2的性能为什么这么好?
微服务架构如何保障双11狂欢下的99.99%高可用?
Spring Cloud Stream如何消费自己生产的消息?
关注我
点击“阅读原文”,看本号其他精彩内容